Ускоряем программу под ArduinoIDE (Arduino NaNO, UNO, MEGA)
Скачать материалы к данной статье
- Если в программе много расчетов, рекомендую заменить числа с плавающей точкой целыми числами.
- Убрать приостановки программы функцией delay(). Использовать временные метки.
- Заменить чтение и запись в GPIO работой с портами ввода вывода.
- Уменьшить точность чтения аналогового сигнала функций analogRead(). https://github.com/avandalen/avdweb_AnalogReadFast/blob/master/avdweb_AnalogReadFast.h
- Не пользоваться функциями задерживающими выполнение программы , например, pulseIn().
- Не использовать Software Serial порты и прочие программные эмуляторы протоколов обмена.
- Отключить вывод данных в Serial порт.
- Где возможно, использовать 8-разрядные числа.
- Применить быстрый контроллер.
Микроконтроллер (ATMega328) Arduino Nano/Uno является довольно медленными и за один такт выполняет только одну 8-и битовую операцию. Этого достаточно в большинстве приложений для данных контроллеров. Если вы используете Arduino NANO с парой датчиков, для анализа обстановки пару раз в секунду, то задумываться об ускорении вашей программы не стоит. А вот если таких датчиков много, допустим пара десятков, подключенных через мультиплексор, с которых считывается аналоговый сигнал, затем сигнал анализируется, обрабатывается и передается на исполнительных механизм, то вы может столкнуться с тем, что ряд показаний датчиков будет теряться, система ошибается «виснет».
Давайте проверим насколько данные рекомендации эффективны
Создадим тестовые программы, в которых масса расчетов производится в цикле, а время начала и окончания цикла запоминается, и выводится в порт для анализа.
1. Пункт - заменить числа с плавающей точкой целыми числами
void setup() { // put your setup code here, to run once: Serial.begin(115200); } void loop() { // Минимальное значение для 16 разрядного, -32768 а максимальное 32767 // выход за эти пределы приведет к потере знаковых разрядов и ошибочным вычислениЯм uint32_t startTime; uint32_t stopTime; uint32_t floatTime; uint32_t intTime; float alfa_f = 0; startTime = micros(); //Запомимнаем микросекунды старта цикла for (int i = -32768; i < 32767; i++) { alfa_f = alfa_f + 10; alfa_f = alfa_f * 2; alfa_f = alfa_f / 3; } stopTime = micros(); //Запомимнаем микросекунды старта цикла floatTime = stopTime - startTime; Serial.print("float Operations ="); Serial.print(floatTime); Serial.println(" micro sec"); // Serial.println(alfa_f); //int8_t alfa_i = 0; //int alfa_i = 0; int32_t alfa_i = 0; startTime = micros(); //Запомимнаем микросекунды старта цикла for (int i = -32768; i < 32767; i++) { alfa_i = alfa_i + 10; alfa_i = alfa_i * 2; alfa_i = alfa_i / 3; } stopTime = micros(); //Запомимнаем микросекунды старта цикла intTime = stopTime - startTime; Serial.print("Integer Operations ="); Serial.print(intTime); Serial.println(" micro sec"); //// Serial.println(alfa_i); Serial.println("***************************"); Serial.print("floatTime-intTime = "); Serial.print(floatTime - intTime); Serial.println(" micro sec"); Serial.print("floatTime/intTime = "); Serial.print(floatTime / intTime); Serial.println(" How much"); Serial.println("***************************"); delay(1000); }
Тестирование примера 1 показывает, что при использовании типа int (целое 16 разрядное знаковое для 8битных плат) в расчетах вместо float (32 разрядное с плавающей точкой) дает ускорение более чем в 3 раза. Использование типа int32_t уже не столь эффективно, а вот использование in8_t , дает ускорение аж в 6 раз.
Отметим, что использовать в расчетах переменные длиной в 1 или 2 байта (int8_t, int16_t) следует только, если значения (даже внутри формулы) в расчетах не превышают пределы (-128/127, -32768/32767). Если возможны, даже промежуточные выходы получаемых в результате расчетов чисел за указанные пределы, то результаты вычислений будут неверными.
2. Пункт - Убрать приостановки программы функцией delay(). Использовать временные метки
void setup() { // put your setup code here, to run once: Serial.begin(115200); } uint32_t goTime = 0; void loop() { // Минимальное значение для 16 разрядного, -32768 а максимальное 32767 // выход за эти пределы приведет к потере знаковых разрядов и ошибочным вычислениЯм if (goTime < millis()) { goTime = millis() + 10000; //+10секунд uint32_t startTime; uint32_t stopTime; uint32_t floatTime; uint32_t intTime; float alfa_f = 0; startTime = micros(); //Запомимнаем микросекунды старта цикла for (int i = -32768; i < 32767; i++) { alfa_f = alfa_f + 10; alfa_f = alfa_f * 2; alfa_f = alfa_f / 3; } stopTime = micros(); //Запомимнаем микросекунды старта цикла floatTime = stopTime - startTime; Serial.print("float Operations ="); Serial.print(floatTime); Serial.println(" micro sec"); // Serial.println(alfa_f); //int8_t alfa_i = 0; //int alfa_i = 0; int32_t alfa_i = 0; startTime = micros(); //Запомимнаем микросекунды старта цикла for (int i = -32768; i < 32767; i++) { alfa_i = alfa_i + 10; alfa_i = alfa_i * 2; alfa_i = alfa_i / 3; } stopTime = micros(); //Запомимнаем микросекунды старта цикла intTime = stopTime - startTime; Serial.print("Integer Operations ="); Serial.print(intTime); Serial.println(" micro sec"); //// Serial.println(alfa_i); Serial.println("***************************"); Serial.print("floatTime-intTime = "); Serial.print(floatTime - intTime); Serial.println(" micro sec"); Serial.print("floatTime/intTime = "); Serial.print(floatTime / intTime); Serial.println(" How much"); Serial.println("***************************"); } }
Допустим, в программе один раз в 10 секунд требуется выводить некоторое значение в порт.
Возьмем в качестве примера расчет из первого пункта. Мы в качестве задержки вывода используем функцию delay(количество миллисекунд задержки), но во время данной задержки никакие расчеты производиться не могут (кроме обработки прерываний), и вычислительная способность программы падает, программа простаивает.
Сделаем так, чтобы наши расчеты выполнялись без задержки, но через 10 секунд. Для этого введем дополнительную глобальную переменную (описана не в функции, а в основной программе)
uint32_t goTime = 0; ,
в которой будем хранить время запуска следующего расчета
if (goTime < millis()) {….}.
В начале расчета записываем в текущее время, увеличенное на 10 секунд
goTime = millis() + 10000; //+10секунд.
Но когда подобных блоков становиться много, программа запутывается. На помощь приходит использование статических переменных внутри функций.
void setup() { // put your setup code here, to run once: Serial.begin(115200); } void loop() { //************************************* loop() // Минимальное значение для 16 разрядного, -32768 а максимальное 32767 // выход за эти пределы приведет к потере знаковых разрядов и ошибочным вычислениЯм testSpeed(); } bool testSpeed() //************************************* testSpeed() { static uint32_t goTime = 0; if (goTime < millis()) { goTime = millis() + 10000; //+10секунд uint32_t startTime; uint32_t stopTime; uint32_t floatTime; uint32_t intTime; float alfa_f = 0; startTime = micros(); //Запомимнаем микросекунды старта цикла for (int i = -32768; i < 32767; i++) { alfa_f = alfa_f + 10; alfa_f = alfa_f * 2; alfa_f = alfa_f / 3; } stopTime = micros(); //Запомимнаем микросекунды старта цикла floatTime = stopTime - startTime; Serial.print("float Operations ="); Serial.print(floatTime); Serial.println(" micro sec"); // Serial.println(alfa_f); //int8_t alfa_i = 0; //int alfa_i = 0; int32_t alfa_i = 0; startTime = micros(); //Запомимнаем микросекунды старта цикла for (int i = -32768; i < 32767; i++) { alfa_i = alfa_i + 10; alfa_i = alfa_i * 2; alfa_i = alfa_i / 3; } stopTime = micros(); //Запомимнаем микросекунды старта цикла intTime = stopTime - startTime; Serial.print("Integer Operations ="); Serial.print(intTime); Serial.println(" micro sec"); //// Serial.println(alfa_i); Serial.println("***************************"); Serial.print("floatTime-intTime = "); Serial.print(floatTime - intTime); Serial.println(" micro sec"); Serial.print("floatTime/intTime = "); Serial.print(floatTime / intTime); Serial.println(" How much"); Serial.println("***************************"); return true; } return false; }
Теперь создана функция
bool testSpeed()
, она вызывается с частотой повторения функции loop()
Но отрабатывает расчет только в том случае, если со времени ее предыдущего вызова прошло 10 секунд.
Мы разгрузили основную функцию loop(), сделали код более читаемым и в то же время сохранили функционал программы.
3. Пункт - Заменить чтение и запись в GPIO работой с портами ввода вывода
(Заменить чтение и запись в GPIO работой с портами ввода вывода)
Отказаться от использования digitalWrite() digitalRead() в пользу прямого обращения к портам.
Но для этого потребуется умение пользоваться булевой алгеброй.
Все PINы Arduino, принадлежат определенному порту ввода вывода, всего портов для NANO и UNO на микроконтроллере Atmega328 три: PB, PD , PC.
Для операций с каждым из портов ввода-вывода используется три регистра: DDRx, PORTx, PINx
DDRx — Data Direction Register. Регистр, определяющий направление, см. команду pinMode(), (1 – передача, 0 – приём).
PORTx — регистр, который содержит логические значения, выводимые на соответствующие GPIO, в случае, если они настроены на вывод.
PINx — регистр, который содержит логические значения, полученные при считывании данных из порта в случае, если они настроены на ввод.
Сначала оттестируем ввод данных с GPIO
void setup() { // put your setup code here, to run once: Serial.begin(115200); pinMode(8,INPUT); } void loop() { //************************************* loop() // Минимальное значение для 16 разрядного, -32768 а максимальное 32767 // выход за эти пределы приведет к потере знаковых разрядов и ошибочным вычислениЯм testSpeed(); } bool testSpeed() //************************************* testSpeed() { static uint32_t goTime = 0; if (goTime < millis()) { goTime = millis() + 10000; //+10секунд uint8_t portB_in_data; int32_t summa = 0; uint32_t startTime; uint32_t stopTime; uint32_t digitalReadTime; uint32_t portTime; startTime = micros(); //Запомимнаем микросекунды старта цикла for (int i = -32768; i < 32767; i++) { portB_in_data = PINB & 0b00000001; //Считываем значение с порта B, накладываем маску для выделения только значения на GPIO D8 (PB0). summa = summa + portB_in_data; } stopTime = micros(); //Запомимнаем микросекунды старта цикла portTime = stopTime - startTime; Serial.print("port input Operations ="); Serial.print(portTime); Serial.println(" micro sec"); // Serial.println(summa); startTime = micros(); //Запомимнаем микросекунды старта цикла for (int i = -32768; i < 32767; i++) { portB_in_data = digitalRead(8); //Считываем значение с GPIO D8 (PB0). summa = summa + portB_in_data; } stopTime = micros(); //Запомимнаем микросекунды старта цикла digitalReadTime = stopTime - startTime; Serial.print("digitalRead(8); Operations ="); Serial.print(digitalReadTime); Serial.println(" micro sec"); // Serial.println(summa); Serial.println("***************************"); Serial.print("digitalReadTime-portTime = "); Serial.print(digitalReadTime - portTime); Serial.println(" micro sec"); Serial.print("digitalReadTime/portTime = "); Serial.print(digitalReadTime / portTime); Serial.println(" How much"); Serial.println("***************************"); return true; } return false; }
Ускорение работы при использовании прямого обращения по чтению к регистру в 5 ПЯТЬ раз!
Теперь тестируем запись. Получаем разницу в скорости выполнения 6 ШЕСТЬ раз.
void setup() { // put your setup code here, to run once: Serial.begin(115200); pinMode(8,OUTPUT); } void loop() { //************************************* loop() // Минимальное значение для 16 разрядного, -32768 а максимальное 32767 // выход за эти пределы приведет к потере знаковых разрядов и ошибочным вычислениЯм testSpeed(); } bool testSpeed() //************************************* testSpeed() { static uint32_t goTime = 0; if (goTime < millis()) { goTime = millis() + 10000; //+10секунд uint8_t portB_in_data; int32_t summa = 0; uint32_t startTime; uint32_t stopTime; uint32_t digitalWriteTime; uint32_t portTime; const uint8_t mask_D8 = 0b11111110; //маска для удаления значение в младше разряде регистра PB (D8) startTime = micros(); //Запомимнаем микросекунды старта цикла for (int i = -32768; i < 32767; i++) { portB_in_data = PORTB & mask_D8; //Выделили значение в порту без младшего разряда PORTB = portB_in_data & 0b00000001; //Добавляем 1 в младший разряд (D8) } stopTime = micros(); //Запомимнаем микросекунды старта цикла portTime = stopTime - startTime; Serial.print("port Write Operations ="); Serial.print(portTime); Serial.println(" micro sec"); // startTime = micros(); //Запомимнаем микросекунды старта цикла for (int i = -32768; i < 32767; i++) { digitalWrite(8,HIGH); //Устанавливаем высокий потенциал на GPIO D8 (PB0). } stopTime = micros(); //Запомимнаем микросекунды старта цикла digitalWriteTime = stopTime - startTime; Serial.print("digitalWrite(8,HIGH); Operations ="); Serial.print(digitalWriteTime); Serial.println(" micro sec"); // Serial.println("***************************"); Serial.print("digitalWriteTime-portTime = "); Serial.print(digitalWriteTime - portTime); Serial.println(" micro sec"); Serial.print("digitalWriteTime/portTime = "); Serial.print(digitalWriteTime / portTime); Serial.println(" How much"); Serial.println("***************************"); return true; } return false; }
Вывод, если ваша программа совершает много операций ввода вывода, то использование прямого обращения к портам, ЕСЛИ ВАШЕЙ ПРОГРАММЕ НЕ ХВАТАЕТ СКОРОСТИ!!!! - Хорошее решение!
4. Пункт - Уменьшить точность чтения аналогового сигнала функций analogRead(). https://github.com/avandalen/avdweb_AnalogReadFast/blob/master/avdweb_AnalogReadFast.h
Уменьшение точности чтения аналогового сигнала функций analogRead().
https://github.com/avandalen/avdweb_AnalogReadFast/blob/master/avdweb_AnalogReadFast.h
Для примера мы использовали сравнение скорости аналогового чтения в стандартном режиме и с уменьшенной точностью. Разница в скорости выполнения также 6 раз.
#include "avdweb_AnalogReadFast.h" void setup() { // put your setup code here, to run once: Serial.begin(115200); } void loop() { //************************************* loop() // Минимальное значение для 16 разрядного, -32768 а максимальное 32767 // выход за эти пределы приведет к потере знаковых разрядов и ошибочным вычислениЯм testSpeed(); } bool testSpeed() //************************************* testSpeed() { static uint32_t goTime = 0; if (goTime < millis()) { goTime = millis() + 10000; //+10секунд uint8_t portB_in_data; int32_t summa = 0; uint32_t startTime; uint32_t stopTime; uint32_t analogReadTime; uint32_t fastAnalogReadTime; // analogReadResolution(12); //только для продвинутых плат startTime = micros(); //Запомимнаем микросекунды старта цикла for (int i = -32768; i < 32767; i++) { summa=summa+analogRead(A0); } stopTime = micros(); //Запомимнаем микросекунды старта цикла analogReadTime = stopTime - startTime; Serial.print(summa); Serial.print(" analogRead() Operations ="); Serial.print(analogReadTime); Serial.println(" micro sec"); // // analogReadResolution(10); //только для продвинутых плат startTime = micros(); //Запомимнаем микросекунды старта цикла for (int i = -32768; i < 32767; i++) { summa=summa+analogReadFast(A0); } stopTime = micros(); //Запомимнаем микросекунды старта цикла fastAnalogReadTime = stopTime - startTime; Serial.print(summa); Serial.print(" fast analogRead() Operations ="); Serial.print(fastAnalogReadTime); Serial.println(" micro sec"); // Serial.println("============================"); Serial.print("analogReadTime-fastAnalogReadTime = "); Serial.print(analogReadTime - fastAnalogReadTime); Serial.println(" micro sec"); Serial.print("analogReadTime/fastAnalogReadTime = "); Serial.print(analogReadTime / fastAnalogReadTime); Serial.println(" How much"); Serial.println("***************************"); return true; } return false; }
7. Пункт убрать все лишние операции вывода данных в Serial порт
В программе ниже мы создали два абсолютно идентичнных цикла, но в первом дополнительно к расчету производится вывод полученногог значения.
void setup() { // put your setup code here, to run once: Serial.begin(115200); } void loop() { //************************************* loop() // Минимальное значение для 16 разрядного, -32768 а максимальное 32767 // выход за эти пределы приведет к потере знаковых разрядов и ошибочным вычислениЯм testSpeed(); } bool testSpeed() //************************************* testSpeed() { static uint32_t goTime = 0; if (goTime < millis()) { goTime = millis() + 10000; //+10секунд uint8_t portB_in_data; int32_t summa = 0; uint32_t startTime; uint32_t stopTime; uint32_t analogReadTime; uint32_t fastAnalogReadTime; // analogReadResolution(12); //только для продвинутых плат startTime = micros(); //Запомимнаем микросекунды старта цикла for (int i = 0; i < 10000; i++) { summa=summa+1; Serial.println(summa); } stopTime = micros(); //Запомимнаем микросекунды старта цикла analogReadTime = stopTime - startTime; Serial.print(summa); Serial.print(" Serial.print() Operations ="); Serial.print(analogReadTime); Serial.println(" micro sec"); // // analogReadResolution(10); //только для продвинутых плат startTime = micros(); //Запомимнаем микросекунды старта цикла for (int i = 0; i < 10000; i++) { summa=summa+1; } stopTime = micros(); //Запомимнаем микросекунды старта цикла fastAnalogReadTime = stopTime - startTime; Serial.print(summa); Serial.print(" Not Serial.print() Operations ="); Serial.print(fastAnalogReadTime); Serial.println(" micro sec"); // Serial.println("============================"); Serial.print("analogReadTime-fastAnalogReadTime = "); Serial.print(analogReadTime - fastAnalogReadTime); Serial.println(" micro sec"); Serial.print("analogReadTime/fastAnalogReadTime = "); Serial.print(analogReadTime / fastAnalogReadTime); Serial.println(" How much"); Serial.println("***************************"); return true; } return false; }
Разница в скорости просто огромная и это в 1250133 раза!!!! Дело в том, что когда буфер отправки порта заполнен, контроллер ждет его освобождения т.е. передачи данных, а скорость передачи несмотря на 115200 бит в секунду мала в сравнении со скоростью расчетов.
ОСТАВИТЬ КОММЕНТАРИЙ
Форма авторизации
ВОЙТИ С ПОМОЩЬЮ:
ИЛИ Авторизация на сайте: